/**
 * @file fiber.cc
 * @brief 协程模块
 * @details
 * Fiber类提供了协程的基本功能
 * 包括创建、管理、切换和销毁协程。它使用了ucontext_t结构(主要使用非对称协程)保存和恢复协程的上下文
 * 并通过std::function来存储协程的执行逻辑。
 */

#include "fiber.h"
#include "macro.h"
#include "scheduler.h"
#include "config.hpp"
#include <memory>
#include <atomic>

// 全局静态变量,用于生成协程ID
static std::atomic<uint64_t> s_fiber_id{0};
// 全局静态变量,用于统计当前的协程数
static std::atomic<uint64_t> s_fiber_count{0};

// 线程局部变量,当前线程正在运行的协程
static thread_local Fiber *t_fiber = nullptr;
// 线程局部变量,当前线程的主协程,切换到这个协程,就相当于切换到了主线程中运行,智能指针形式
static thread_local Fiber::ptr t_thread_fiber = nullptr;

// malloc栈分配内存器
class MallocStackAllocator
{
public:
    static void *Alloc(size_t size) { return malloc(size); }
    static void Dealloc(void *vp, size_t size) { return free(vp); }
};

using StackAllocator = MallocStackAllocator;

/**
 * @brief 构造函数
 * @attention 无参构造函数只能用于创建线程的第一个协程,也就是主函数对应的协程
 * 这个协程只能由GetThis方法调用,所以定义为私有方法
 * @details
 * 作用:创建主协程,设置状态,初始化上下文,分配ID
 */
Fiber::Fiber()
{
    SetThis(this);
    m_statu = RUNNING;

    if (getcontext(&m_ctx))
    {
        SYLAR_ASSERT2(false, "getcontext");
    }

    ++s_fiber_count;
    m_id = s_fiber_id++; // 协程id从0开始,用完加1

    LOG(INFO, "Fiber::Fiber() main id = %d", m_id);
}

void Fiber::SetThis(Fiber *fiber)
{
    t_fiber = fiber;
}

// 获取当前协程,同时充当初始化当前线程主协程的作用,这个函数在使用协程之前要调用一下
Fiber::ptr Fiber::GetThis()
{
    if (t_fiber)
    {
        return t_fiber->shared_from_this();
    }

    /**
     * @details  创建智能指针的时候会调用无参数的构造函数
     * t_fiber就被进行了赋值
     */

    Fiber::ptr main_fiber(new Fiber);
    assert(t_fiber == main_fiber.get());
    t_thread_fiber = main_fiber;

    return t_fiber->shared_from_this();
}

// 带参数的构造函数用于创建其他协程,需要分配栈
// 作用:创建一个新协程,初始化回调函数,栈的大小和状态
Fiber::Fiber(const std::function<void()>& cb, size_t stacksize, bool run_in_scheduler)
    : m_id(s_fiber_id++), m_cb(cb), m_runInScheduler(run_in_scheduler)
{
    ++s_fiber_count;
    m_stacksize = stacksize ? stacksize : Config::GetInstance().fiber_stack_size;

    m_stack = StackAllocator::Alloc(m_stacksize);

    if (getcontext(&m_ctx))
    {
        SYLAR_ASSERT2(false, "getcontext");
    }

    // 因为这里没有设置,所以在运行完Mainfunc后协程退出,会调用一次yield返回主协程
    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;

    makecontext(&m_ctx, &Fiber::MainFunc, 0);

    LOG(INFO, "Fiber::Fiber() id = %d", m_id);
}

// 协程的主协程析构函数需要特殊处理,因为主协程没有分配栈和cb
Fiber::~Fiber()
{
    LOG(DEBUG, "Fiber::Fiber() id = %d", m_id);
    --s_fiber_count;

    if (m_stack)
    {
        // 有栈,说明是子协程,需要确保子协程一定是结束状态
        SYLAR_ASSERT(m_statu == TERM);
        StackAllocator::Dealloc(m_stack, m_stacksize);
        LOG(DEBUG, "dealloc stack, id = %d", m_id);
    }
    else
    {
        // 没有栈,说明是主线程的主协程
        SYLAR_ASSERT(!m_cb);              // 主协程没有cb
        SYLAR_ASSERT(m_statu == RUNNING); // 主协程一定是执行状态

        Fiber *cur = t_fiber; // 当前协程就是自己
        if (cur == this)
        {
            SetThis(nullptr);
        }
    }
}

// 获取当前协程的ID
uint64_t Fiber::GetFiberId()
{
    if (t_fiber)
    {
        return t_fiber->getId();
    }
    return 0;
}

// 这里为了简化状态管理,强制只有TERM状态的协程才可以重置,但其实刚创建好的但没有执行过的协程也应该允许重置的
// 重用一个已经终止的协程对象，从而避免频繁创建和销毁对象带来的开销。通过重置协程的状态和任务，可以在执行新的任务时重新利用这个协程对像。
void Fiber::reset(std::function<void()> cb)
{
    SYLAR_ASSERT(m_stack);
    SYLAR_ASSERT(m_statu == TERM);

    m_cb = cb;
    if (getcontext(&m_ctx))
    {
        SYLAR_ASSERT2(false, "getcontext");
    }

    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;

    makecontext(&m_ctx, &Fiber::MainFunc, 0);
    m_statu = READY;
}

// 作用:将协程的专题图设置为RUNNING,并恢复协程的执行
// 如果m_runInScheduler为true,则将上下文切换到调度协程,否则切换到主线程的协程
void Fiber::resume()
{
    SYLAR_ASSERT(m_statu != TERM && m_statu != RUNNING);
    SetThis(this);
    m_statu = RUNNING;

    // 如果协程参与调度器调度,那么应该和调度器的主协程进行swap,而不是线程主协程
    if (m_runInScheduler)
    {
        // 切换到调度协程
        if (swapcontext(&(Scheduler::GetMainFiber()->m_ctx), &m_ctx))
        {
            SYLAR_ASSERT2(false, "swapcontext");
        }
    }
    else
    {
        // 切换到线程主协程
        if (swapcontext(&(t_thread_fiber->m_ctx), &m_ctx))
        {
            SYLAR_ASSERT2(false, "swapcontext");
        }
    }
}

void Fiber::yield()
{
    // 协程运行完之后会自动yield一次,用于回到主协程,此时状态已为结束状态
    SYLAR_ASSERT(m_statu == TERM || m_statu == RUNNING);
    SetThis(t_thread_fiber.get());
    if (m_statu != TERM)
    {
        m_statu = READY;
    }

    // 如果协程参与调度器调度,那么应该和调度器的主协程进行swap,而不是线程主协程
    if (m_runInScheduler)
    {
        if (swapcontext(&m_ctx, &(Scheduler::GetMainFiber()->m_ctx)))
        {
            SYLAR_ASSERT2(false, "swapcontext");
        }
    }
    else
    {
        if (swapcontext(&m_ctx, &(t_thread_fiber->m_ctx)))
        {
            SYLAR_ASSERT2(false, "swapcontext");
        }
    }
}

/**
 * 这里没有处理协程函数出现异常的情况,同样是为了简化状态管理,协程的异常不应该由框架处理,应该由开发者自行处理
 */
void Fiber::MainFunc()
{
    // GetThis()的shared_from_this()让引用计数+1
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur);

    cur->m_cb();
    cur->m_cb = nullptr;
    cur->m_statu = TERM;

    // 手动让t_fuber的引用计数-1
    auto raw_ptr = cur.get();
    cur.reset();

    raw_ptr->yield();
}
